package org.raymond.iworks.study.spring.service;

import lombok.extern.slf4j.Slf4j;
import org.raymond.iworks.commons.utils.SnowFlake;
import org.raymond.iworks.repository.entity.TSysLog;
import org.raymond.iworks.repository.repository.TSysLogRepository;
import org.raymond.iworks.repository.repository.TSysUserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;

/**
 * @author raymond
 * @version V1.0
 * @Description: TODO
 */
@Slf4j
@Service
public class ParallelService {
    @Autowired
    private TSysLogRepository tSysLogRepository;
    @Autowired
    private TSysUserRepository tSysUserRepository;

    @Autowired
    private TransactionTemplate transactionTemplate;

    private static final ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 3,
                0, TimeUnit.SECONDS, new LinkedBlockingQueue(), (r)->{
        Thread t = new Thread(r);
        return t;
    });

    public void asyncInsertA() throws Exception {
        CompletableFuture<Void> f1 = CompletableFuture.runAsync(()->{
            transactionTemplate.execute(status->{
                log.info("async insert start");
                try {
                    insertLog(true);
                }catch (RuntimeException e){
                    log.error("{}", e.getMessage());
                    status.setRollbackOnly();
                }
                return status;
            });
        });
        f1.join();
        log.info("async insert end");
    }

    public void asyncInsertB() throws Exception {
        AtomicBoolean rollback = new AtomicBoolean(false);
        List<Thread> tList = new ArrayList<>();
        CyclicBarrier barrier = new CyclicBarrier(3);
        Function<Boolean, Integer> c = this::insertLog;
        Runnable r1 = newTxRunnable(c, false, rollback, barrier, tList);
        Runnable r2 = newTxRunnable(c, true, rollback, barrier, tList);
        pool.execute(r1);
        pool.execute(r2);

        // 等待所有业务线程执行完毕
        log.info("main wait start {}", tList.size());
        barrier.await();
        // 所有业务线程执行完毕
        log.info("main wait end {}", tList.size());
    }

    public Runnable newTxRunnable(Function<Boolean, Integer> apply, Boolean b,
                                  AtomicBoolean rollback, CyclicBarrier barrier, List<Thread> threads){
        threads.add(Thread.currentThread());
        return ()->transactionTemplate.execute(status->{
            Integer i = 0;
            try {
                i = apply.apply(b);
            }catch (Exception e){
                log.error("{}", e.getMessage());
                rollback.set(true);
                i = 0;
            }
            try {
                // 业务执行完后等待其他线程执行完毕
                log.info("await start {} {}", Thread.currentThread().getName(), rollback.get());
                barrier.await();
                // 其他线程都执行完毕
                log.info("await end {} {}", Thread.currentThread().getName(), rollback.get());
            } catch (InterruptedException | BrokenBarrierException e) {
                log.error("{}", e.getMessage());
            }
            if(rollback.get()){
                status.setRollbackOnly();
            }
            return i;
        });
    }

    public int insertLog(boolean ex) throws RuntimeException {
        TSysLog sysLog = newLog("parallel.insert");
        TSysLog saved = tSysLogRepository.save(sysLog);
        if(ex){
            throw new RuntimeException("throw ex");
        }
        Long id = saved.getId();
        return id>0?1:0;
    }

    private TSysLog newLog(String request){
        Date current = new Date();
        TSysLog log = TSysLog.builder()
                .id(SnowFlake.generateId())
                .method("POST")
                .clientTime(current)
                .startTime(current)
                .endTime(current)

                .request(request)
                .build();
        return log;
    }
}
